package com.jpro.framework.security.core.util;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author jprocms
 */
public class RequestUtils {

    private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class);

    public static final String COMMON_PATH_SEPARATE = "-";
    public static final int PORT_DEF = 80;

    private static final String UN_KNOWN = "unknown";
    private static final String ESCAPE_AND_STR = "&amp;";
    private static final String[] ADD_HEADER = {"X-Forwarded-For", "Proxy-Client-IP",
            "WL-Proxy-Client-IP", "X-Real-IP", "X-Forwarded-Scheme"};
    private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?");
    /**
     * 获取当前访问URL （含协议、域名、端口号[80端口默认忽略]、项目名）
     *
     * @param request HttpServletRequest
     * @Title: getServerUrl
     * @return: String
     */
    public static String getServerUrl(HttpServletRequest request) {
        // 访问协议
        //String agreement = request.getScheme();
        //SSL 认证之后，request.getScheme()获取不到https的问题记录
        //https://www.cnblogs.com/start-fxw/p/7890337.html
        //在nginx 配置location处加上proxy_set_header X-Forwarded-Scheme  $scheme;
        String agreement = request.getHeader("X-Forwarded-Scheme");
        //可能为null
        if (StringUtils.isBlank(agreement)) {
            agreement = request.getScheme();
        }
        // 访问域名
        String serverName = request.getServerName();
        // 访问端口号
        int port = request.getServerPort();
        // 访问项目名
        String contextPath = request.getContextPath();
        String url = "%s://%s%s%s";
        String portStr = "";
        if (port != PORT_DEF) {
            portStr += ":" + port;
        }
        return String.format(url, agreement, serverName, portStr, contextPath);
    }



    /**
     * 获取当前访问URL （含协议、域名、端口号[80端口默认忽略]）
     *
     * @param request
     * @return
     */
    public static String getServerUrlHeader(HttpServletRequest request) {
        // 访问协议
        String agreement = request.getScheme();
        // 访问域名
        String serverName = request.getServerName();
        // 访问端口号
        int port = request.getServerPort();
        String url = "%s://%s%s";
        String portStr = "";
        if (port != PORT_DEF) {
            portStr += ":" + port;
        }
        return String.format(url, agreement, serverName, portStr);

    }

    /**
     * 优先获取header中值，没有则从param中获取
     *
     * @param request HttpServletRequest
     * @param header  header标识符
     * @Title: getHeaderOrParam
     * @return: String
     */
    public static String getHeaderOrParam(HttpServletRequest request, String header) {
        // 尝试获取请求头的 token
        String headerVal = request.getHeader(header);
        if (StringUtils.isBlank(headerVal)) {
            headerVal = RequestUtils.getParam(request, header);
        }
        return headerVal;
    }

    public static String getRequestString(HttpServletRequest req) {
        String requestPath = req.getServletPath().toString();
        String queryString = req.getQueryString();
        if (queryString != null) {
            return requestPath + "?" + queryString;
        } else {
            return requestPath;
        }
    }

    public static MultiValueMap<String, String> parseMultivalueQueryString(@Nullable String queryString) {
        // 使用 org.apache.http.client.utils.URLEncodedUtils 实现
        // 使用 split 实现，性能最差。
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        if (StringUtils.isNotBlank(queryString)) {
            Matcher matcher = QUERY_PARAM_PATTERN.matcher(queryString);
            while (matcher.find()) {
                String name = matcher.group(1);
                String eq = matcher.group(2);
                String value = matcher.group(3);
                try {
                    String decodeValue;
                    if (value != null) {
                        decodeValue = URLDecoder.decode(value, "UTF-8");
                    } else {
                        decodeValue = StringUtils.isNotBlank(eq) ? "" : null;
                    }
                    queryParams.add(URLDecoder.decode(name, "UTF-8"), decodeValue);
                } catch (UnsupportedEncodingException e) {
                    logger.error("never!", e);
                }
            }
        }
        return queryParams;
    }

    /**
     * 获得真实IP地址。在使用了反向代理时，直接用HttpServletRequest.getRemoteAddr()无法获取客户真实的IP地址。
     *
     * @param request ServletRequest
     * @return
     */
    public static String getRemoteAddr(ServletRequest request) {
        String addr = null;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest hsr = (HttpServletRequest) request;
            for (String header : ADD_HEADER) {
                if (StringUtils.isBlank(addr) || UN_KNOWN.equalsIgnoreCase(addr)) {
                    addr = hsr.getHeader(header);
                } else {
                    break;
                }
            }
        }
        if (StringUtils.isBlank(addr) || UN_KNOWN.equalsIgnoreCase(addr)) {
            addr = request.getRemoteAddr();
        } else {
            // 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按','分割
            if (addr != null) {
                int i = addr.indexOf(",");
                if (i > 0) {
                    addr = addr.substring(0, i);
                }
            }

        }
        return addr;
    }

    /**
     * 获得当的访问路径
     * HttpServletRequest.getRequestURL+"?"+HttpServletRequest.getQueryString
     *
     * @param request HttpServletRequest
     * @return
     */
    public static String getLocation(HttpServletRequest request) {
        UrlPathHelper helper = new UrlPathHelper();
        StringBuffer buff = request.getRequestURL();
        String uri = request.getRequestURI();
        String origUri = helper.getOriginatingRequestUri(request);
        buff.replace(buff.length() - uri.length(), buff.length(), origUri);
        String queryString = helper.getOriginatingQueryString(request);
        if (queryString != null) {
            buff.append("?").append(queryString);
        }
        try {
            return new String(buff.toString().getBytes(), "iso-8859-1");
        } catch (UnsupportedEncodingException e) {
            return buff.toString();
        }
    }

    /**
     * 获取当前会话中的HttpServletRequest
     *
     * @Title: getHttpServletRequest
     * @return: HttpServletRequest
     */
    public static HttpServletRequest getHttpServletRequest() {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (sra != null) {
            return sra.getRequest();
        }
        return null;
    }

    /**
     * 获取当前会话中的HttpServletRequest
     *
     * @Title: getHttpServletRequest
     * @return: HttpServletRequest
     */
    public static HttpServletResponse getHttpServletResponse() {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (sra != null) {
            return sra.getResponse();
        }
        return null;
    }

    /**
     * 获取当前请求地址，不含协议、主机、端口、部署路径及参数
     *
     * @return
     */
    public static String getRequestUrl(HttpServletRequest request) {
        String uri = request.getRequestURI();
        String ctx = request.getContextPath();
        if (StringUtils.isBlank(uri)) {
            return "";
        }
        if (StringUtils.isBlank(ctx)) {
            return uri;
        }
        return uri.substring(uri.indexOf(ctx) + ctx.length());
    }

    /**
     * 获取请求参数
     *
     * @param request HttpServletRequest
     * @Title: getQueryParams
     * @return: Map
     */
    public static Map<String, Object> getQueryParams(HttpServletRequest request) {
        Map<String, String[]> map;
        // if (request.getMethod().equalsIgnoreCase(POST)) {
        if (request.getMethod().equalsIgnoreCase("POST")) {
            map = request.getParameterMap();
        } else {
            String s = request.getQueryString();
            if (StringUtils.isBlank(s)) {
                return new HashMap<String, Object>(5);
            }
            try {
                s = s.replaceAll("%(?![\\da-fA-F]{2})", "%25");
                s = s.replaceAll("\\+", "%2B");
                // s = URLDecoder.decode(s, UTF8);
                s = URLDecoder.decode(s, "UTF8");
            } catch (UnsupportedEncodingException e) {
                s = request.getQueryString();
            }
            map = parseQueryString(s);
        }

        Map<String, Object> params = new HashMap<String, Object>(map.size());
        int len;
        for (Map.Entry<String, String[]> entry : map.entrySet()) {
            len = entry.getValue().length;
            if (len == 1) {
                params.put(entry.getKey(), entry.getValue()[0]);
            } else if (len > 1) {
                params.put(entry.getKey(), entry.getValue());
            }
        }
        return params;
    }

    /**
     * 获取请求参数map
     *
     * @param queryString queryString
     * @Title: parseQueryString
     * @return: Map
     */
    public static Map<String, String[]> parseQueryString(String queryString) {
        if (StringUtils.isBlank(queryString)) {
            return Collections.emptyMap();
        }
        Map<String, String[]> queryMap = new TreeMap<String, String[]>();
        String[] params;
        /** &被JsoupUtil转移 */
        if (queryString.indexOf(ESCAPE_AND_STR) != -1) {
            params = queryString.split(ESCAPE_AND_STR);
        } else {
            params = queryString.split("&");
        }

        for (String param : params) {
            int index = param.indexOf('=');
            if (index != -1) {
                String name = param.substring(0, index);
                // name为空值不保存
                if (StringUtils.isBlank(name)) {
                    continue;
                }
                String value = param.substring(index + 1);
                try {
                    /**URLDecoder: Incomplete trailing escape (%) pattern*/
                    value = value.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
                    value = value.replaceAll("\\+", "%2B");
                    value = URLDecoder.decode(value, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    logger.error("never!", e);
                }
                if (queryMap.containsKey(name)) {
                    String[] values = queryMap.get(name);
                    queryMap.put(name, ArrayUtils.addAll(values, value));
                } else {
                    queryMap.put(name, new String[] { value });
                }
            }
        }
        return queryMap;
    }

    public static String getParam(HttpServletRequest request, Map<String, String[]> queryMap,
            String name) {
        String[] values = getParamValues(request, queryMap, name);
        return ArrayUtils.isNotEmpty(values) ? StringUtils.join(values, ',') : null;
    }

    public static String getParam(HttpServletRequest request, String name) {
        String[] values = getParamValues(request, name);
        return ArrayUtils.isNotEmpty(values) ? StringUtils.join(values, ',') : null;
    }

    /**
     * 获取参数值 数组
     *
     * @param request  HttpServletRequest
     * @param queryMap Map
     * @param name     参数值名称
     * @Title: getParamValues
     * @return: String[]
     */
    public static String[] getParamValues(HttpServletRequest request,
            Map<String, String[]> queryMap, String name) {
        Validate.notNull(request, "Request must not be null");
        String[] values = queryMap.get(name);
        if (values == null) {
            values = request.getParameterValues(name);
        }
        Object a = request.getAttribute(name);
        return values;
    }

    /**
     * 获取参数值 数组
     *
     * @param request HttpServletRequest
     * @param name    参数值名称
     * @Title: getParamValues
     * @return: String[]
     */
    public static String[] getParamValues(HttpServletRequest request, String name) {
        Validate.notNull(request, "Request must not be null");
        String qs = request.getQueryString();
        Map<String, String[]> queryMap = parseQueryString(qs);
        return getParamValues(request, queryMap, name);
    }

    /**
     * 根据参数名前缀获取值数组
     *
     * @param request HttpServletRequest
     * @param prefix  参数名前缀
     * @Title: getParamPrefix
     * @return: String[]
     */
    public static String[] getParamPrefix(HttpServletRequest request, String prefix) {
        Validate.notNull(request, "Request must not be null");
        Map<String, String[]> params = getParamValuesMap(request, prefix);
        List<String> values = new ArrayList<String>();
        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            values.addAll(Arrays.asList(entry.getValue()));
        }
        return values.toArray(new String[values.size()]);
    }

    public static Map<String, String> getParamMap(HttpServletRequest request, String prefix) {
        return getParamMap(request, prefix, false);
    }

    /**
     * 根据参数名前缀 获取参数名 map
     *
     * @param request       HttpServletRequest
     * @param prefix        参数名前缀
     * @param keyWithPrefix 是否完全匹配，true指定参数名 false 则按前缀查参数
     * @Title: getParamMap
     * @return: Map
     */
    public static Map<String, String> getParamMap(HttpServletRequest request, String prefix,
            boolean keyWithPrefix) {
        Validate.notNull(request, "Request must not be null");
        Map<String, String> params = new LinkedHashMap<String, String>();
        if (prefix == null) {
            prefix = "";
        }
        String qs = request.getQueryString();
        Map<String, String[]> queryMap = parseQueryString(qs);
        int len = prefix.length();
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames != null && paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            if ("".equals(prefix) || paramName.startsWith(prefix)) {
                String name = keyWithPrefix ? paramName : paramName.substring(len);
                String value = getParam(request, queryMap, paramName);
                if (StringUtils.isNotBlank(value)) {
                    params.put(name, value);
                }
            }
        }
        return params;
    }

    public static Map<String, String[]> getParamValuesMap(HttpServletRequest request,
            String prefix) {
        return getParamValuesMap(request, prefix, false);
    }

    /**
     * 获取参数值map
     *
     * @param request HttpServletRequest
     * @param patters field_operate_dataType格式通用查询参数数组
     * @Title: getParamValuesMap
     * @return: Map
     */
    public static Map<String, String[]> getParamValuesMap(HttpServletRequest request,
            String[] patters) {
        Map<String, String[]> params = getParamValuesMap(request, null, true);
        Map<String, Map<String, String>> patterMap = new HashMap<String, Map<String, String>>(5);
        if (patters != null && patters.length > 0) {
            // 格式化参数标准数据中，将name_eq或name_eq_String 转换成以 _ 分隔成name为key ,
            // operate、dataType、field为key的map
            // operate(操作方式)、dataType(条件数据类型)、field(对应实体类属性名)
            for (int i = 0; i < patters.length; i++) {
                String[] patter = patters[i].split("_");
                if (patter.length < 2) {
                    throw new IllegalArgumentException(
                            " " + "patters formate error , must include '-' ");
                }
                Map<String, String> map = new HashMap<String, String>(5);
                map.put("operate", patter[1]);
                if (patter.length > 2) {
                    map.put("dataType", patter[2]);
                }
                if (patter[0].startsWith("[") && patter[0].endsWith("]")) {
                    String[] str = patter[0].replace("[", "").replace("]", "").split(",");
                    if (str.length < 2) {
                        throw new IllegalArgumentException(
                                " \"[]\"patters formate error ," + " must include ',' ");
                    }
                    map.put("field", str[1]);
                    patterMap.put(str[0], map);
                } else {
                    map.put("field", patter[0]);
                    patterMap.put(patter[0], map);
                }
            }
        } else {
            return new HashMap<String, String[]>(1);
        }
        Map<String, String[]> comParams = new HashMap<String, String[]>(5);
        int includeCount = 0;
        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            // 参数
            String key = entry.getKey();
            // 判断参数是否在标准格式中是否存在
            if (patterMap.containsKey(entry.getKey())) {
                String[] value = entry.getValue();
                Map<String, String> patterValue = patterMap.get(key);
                String comKey =
                        patterValue.get("operate").toUpperCase() + "_" + patterValue.get("field");
                if (patterValue.containsKey("dataType")) {
                    comKey += "_" + patterValue.get("dataType");
                }
                comParams.put(comKey, value);
            } else {
                includeCount++;
            }
        }
        if (includeCount > 0) {
            logger.info("查询参数不符合规则，已自动过滤{}个", includeCount);
        }
        return comParams;
    }

    /**
     * 获取请求参数值
     *
     * @param request       HttpServletRequest
     * @param prefix        参数名前缀
     * @param keyWithPrefix 是否完全匹配，true指定参数名 false 则按前缀查参数
     * @Title: getParamValuesMap
     * @return: Map
     */
    public static Map<String, String[]> getParamValuesMap(HttpServletRequest request, String prefix,
            boolean keyWithPrefix) {
        Validate.notNull(request, "Request must not be null");
        Enumeration<String> paramNames = request.getParameterNames();
        Map<String, String[]> params = new LinkedHashMap<String, String[]>();
        if (prefix == null) {
            prefix = "";
        }
        String qs = request.getQueryString();
        Map<String, String[]> queryMap = parseQueryString(qs);
        int len = prefix.length();
        while (paramNames != null && paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            if ("".equals(prefix) || paramName.startsWith(prefix)) {
                String name = keyWithPrefix ? paramName : paramName.substring(len);
                String[] values = getParamValues(request, queryMap, paramName);
                if (values != null && values.length > 0) {
                    params.put(name, values);
                }
            }
        }
        return params;
    }

}
